﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;

namespace Paril
{
	public class PakFile
	{
		public const int PAK_HEADER			= (('K'<<24)+('C'<<16)+('A'<<8)+'P');	// little-endian "PACK"
		public const string PAK_HEADERSTR	= "PACK";

		public const int PAK_MAX_FILES		= 4096;

		void RecursiveAdd(List<PakFile.Directory> dirs, PakFile.Directory dir)
		{
			foreach (var d in dir.SubDirectories)
			{
				dirs.Add(d);
				RecursiveAdd(dirs, d);
			}
		}

		void RecursiveAdd(List<PakFile.File> files, PakFile.Directory dir)
		{
			foreach (var d in dir.Files)
				files.Add(d);

			foreach (var d in dir.SubDirectories)
				RecursiveAdd(files, d);
		}

		public PakFile.Directory[] GetDirectories()
		{
			List<PakFile.Directory> dirs = new List<Directory>();

			dirs.Add(MainDirectory);
			RecursiveAdd(dirs, MainDirectory);

			return dirs.ToArray();
		}

		public PakFile.File[] GetFiles()
		{
			List<PakFile.File> files = new List<File>();

			RecursiveAdd(files, MainDirectory);

			return files.ToArray();		
		}

		public class File
		{
			public string Name;
			public byte[] Data;

			public MemoryStream GetStream()
			{
				return new MemoryStream(Data);
			}
		}

		public File RecursiveFind(string[] split, int curIndex, Directory dir)
		{
			if (split[curIndex].LastIndexOf('.') != -1 || curIndex == split.Length - 1)
			{
				foreach (var f in dir.Files)
				{
					if (f.Name == split[curIndex])
						return f;
				}

				return null;
			}

			foreach (var d in dir.SubDirectories)
			{
				if (d.Name == split[curIndex])
					return RecursiveFind(split, curIndex + 1, d);
			}

			return null;
		}

		public File FindFile(string FileName)
		{
			string[] split = FileName.Split('\\');

			return RecursiveFind(split, 0, MainDirectory);
		}

		void RecursiveFindFiles(System.Collections.ObjectModel.Collection<File> files, string Filter, Directory dir, SearchOption Options)
		{
			foreach (var f in dir.Files)
			{
				if (f.Name.CompareWildcards(Filter, true))
					files.Add(f);
			}

			if (Options == SearchOption.AllDirectories)
			{
				foreach (var d in dir.SubDirectories)
					RecursiveFindFiles(files, Filter, d, Options);
			}
		}

		public System.Collections.ObjectModel.Collection<File> FindFiles(string Filter, System.IO.SearchOption Options = SearchOption.TopDirectoryOnly)
		{
			System.Collections.ObjectModel.Collection<File> files = new System.Collections.ObjectModel.Collection<File>();
			RecursiveFindFiles(files, Filter, MainDirectory, Options);
			return files;
		}

		public class Directory
		{
			public string Name;

			public List<Directory> SubDirectories = new List<Directory>();
			public List<File> Files = new List<File>();
		}

		public Directory MainDirectory = new Directory();

		struct PackFile
		{
			public const int MaxName = 56;
			public const int StructureSize = (MaxName + 4 + 4);

			public string name;

			public int filePos;
			public int fileLen;

			public void Read(BinaryReader reader)
			{
				name = VCMDL.NET.CCString.Read(reader, MaxName).Replace('/', '\\');
				filePos = reader.ReadInt32();
				fileLen = reader.ReadInt32();
			}

			public File ToFile(BinaryReader reader)
			{
				File f = new File();

				f.Name = name;

				if (f.Name.LastIndexOf('\\') != -1)
					f.Name = f.Name.Substring(f.Name.LastIndexOf('\\')+1);

				long oldPos = reader.BaseStream.Position;
				reader.BaseStream.Position = filePos;
				f.Data = reader.ReadBytes(fileLen);
				reader.BaseStream.Position = oldPos;

				return f;
			}
		}

		struct Header
		{
			public int ident;		// == PAK_HEADER
			public int dirOfs;
			public int dirLen;

			public void Read(BinaryReader reader)
			{
				ident = reader.ReadInt32();
				dirOfs = reader.ReadInt32();
				dirLen = reader.ReadInt32();
			}
		}

		public PakFile()
		{
		}

		public PakFile(string FileName)
		{
			Load(FileName);
		}

		public Directory MakeDir(string Dir)
		{
			if (Dir.LastIndexOf('\\') == -1)
				return MainDirectory;

			if (Dir.LastIndexOf('.') != -1)
				Dir = Dir.Substring(0, Dir.LastIndexOf('\\'));

			Directory curDir = null;
			string[] dirs = Dir.Split('\\');

			for (int i = 0; i < dirs.Length; ++i)
			{
				string d = dirs[i];
				List<Directory> checkList = (curDir == null) ? MainDirectory.SubDirectories : curDir.SubDirectories;
				bool Retry = false;

				for (int z = 0; z < checkList.Count; ++z)
				{
					if (checkList[z].Name == d)
					{
						curDir = checkList[z];
						Retry = true;
						break;
					}
				}

				if (Retry)
					continue;

				curDir = new Directory();
				curDir.Name = d;
				checkList.Add(curDir);
			}

			return curDir;
		}

		public void Load(string FileName)
		{
			using (var f = System.IO.File.Open(FileName, FileMode.Open))
			{
				using (var reader = new BinaryReader(f))
				{
					Header head = new Header();
					head.Read(reader);

					if (head.ident != PAK_HEADER)
						throw new FileLoadException("Not a pak file");

					int numFiles = head.dirLen / PackFile.StructureSize;

					if (numFiles > PAK_MAX_FILES)
						throw new FileLoadException("Too many files");
					else if (numFiles <= 0)
						return;

					reader.BaseStream.Position = head.dirOfs;

					for (int i = 0; i < numFiles; ++i)
					{
						PackFile file = new PackFile();
						file.Read(reader);

						Directory storeDir = MakeDir(file.name);
						storeDir.Files.Add(file.ToFile(reader));
					}
				}
			}
		}
	}
}
